package service

import (
	"bufio"
	"errors"
	"fmt"
	"gin-luban-server/global"
	"gin-luban-server/model"
	"github.com/gorilla/websocket"
	"github.com/pkg/sftp"
	gossh "golang.org/x/crypto/ssh"
	"net"
	"os"
	"strconv"
	"text/template"
	"time"
	"unicode/utf8"
)

    type tplHostData struct {
		template         *template.Template
		locationPath     string
		autoConfigPath   string
	}


//@author: heyibo
//@function: NewAnsibleClient
//@description:
//@return:
func NewAnsibleClient(Cols,Rows uint32,id uint) (client *gossh.Client, ch gossh.Channel, err error)  {
	err,server :=GetCmdbServer(id)
	if err !=nil {
		return nil,nil,err
	}
    var basic model.SysBasicConfigure
	err = global.GVA_DB.Model(&model.SysBasicConfigure{}).Where("purpose = ? And server_idc = ? ","ansible",server.ServerIdc).First(&basic).Error
	if err != nil {
		return nil,nil,err
	}
	config := gossh.Config{
		Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
	}
	clientConfig := &gossh.ClientConfig{
		User:    basic.BasicUser,
		Timeout: 300 * time.Second,
		Config:  config,
		HostKeyCallback: func(hostname string, remote net.Addr, key gossh.PublicKey) error {
			return nil
		},
	}
	switch basic.SshType {
	case "password":
		clientConfig.Auth = []gossh.AuthMethod{gossh.Password(basic.BasicPasswd)}
	case "key":
		signer, _ := gossh.ParsePrivateKey([]byte(basic.SshKey))
		clientConfig.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)}
	default:
		return nil, nil,fmt.Errorf("unknow ssh auth type: %s", basic.SshType)
	}
	port,_ :=strconv.Atoi(basic.ProxyPort)
	addr := fmt.Sprintf("%s:%d", basic.ProxyHost, port)
	client, err = gossh.Dial("tcp", addr, clientConfig);
	if err != nil {
		return nil,nil,err
	}
	channel, incomingRequests, err := client.Conn.OpenChannel("session", nil)
	if err != nil {
		return
	}
	go func() {
		for req := range incomingRequests {
			if req.WantReply {
				req.Reply(false, nil)
			}
		}
	}()
	modes := gossh.TerminalModes{
		gossh.ECHO:          1,
		gossh.TTY_OP_ISPEED: 14400,
		gossh.TTY_OP_OSPEED: 14400,
	}
	var modeList []byte
	for k, v := range modes {
		kv := struct {
			Key byte
			Val uint32
		}{k, v}
		modeList = append(modeList, gossh.Marshal(&kv)...)
	}
	modeList = append(modeList, 0)
	req := ptyRequestMsg{
		Term:     "xterm",
		Columns:  Cols,
		Rows:     Rows,
		Width:    Cols * 8,
		Height:   Rows * 8,
		Modelist: string(modeList),
	}
	ok, err := channel.SendRequest("pty-req", true, gossh.Marshal(&req))
	if err != nil {
		return
	}
	if !ok {
		err = errors.New("e001")
		return
	}
	ok, err = channel.SendRequest("shell", true, nil)
	if err != nil {
		return
	}
	if !ok {
		err = errors.New("e002")
		return
	}
	ch = channel
	return
}


//@author: heyibo
//@function: CreateHostTemp
//@description:
//@return:
func CreateHostTemp(sftpClient *sftp.Client,id int) (err error) {
		var list model.CmdbServer
		configPath := "resource/ansible/host.tpl"
		data := tplHostData{
			locationPath: configPath,
			autoConfigPath: "hosts",
		}
		err = global.GVA_DB.Model(&model.CmdbServer{}).Where("id = ?",id).Find(&list).Error
		// 生成 *Template, 填充 template 字段
		data.template, err = template.ParseFiles(data.locationPath)
		if err != nil {
			return err
		}
		//autoPath := "resource/ansible/"
		data.autoConfigPath =  data.autoConfigPath
	    localFilePath :=  data.autoConfigPath
	    remotePath :="/home/appuser"
		// 生成文件
		f, err := os.OpenFile(data.autoConfigPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
		defer f.Close()
		if err != nil {
			return err
		}
		if err = data.template.Execute(f, list); err != nil {
			return err
		}
		if err = UploadFile(sftpClient,localFilePath,remotePath);err != nil {
			return err
		}
		return err
}

//@author: heyibo
//@function: AnsibleHostCmd
//@description:
//@return:
func AnsibleWebSocketHandler(ws *websocket.Conn,client *gossh.Client, channel gossh.Channel,isAdmin bool,id int,cmd string) {
	if isAdmin {
		sftpClient,errSftp:=sftp.NewClient(client)
		if errSftp != nil {
			return
		}
		err := CreateHostTemp(sftpClient,id)
		if err != nil {
			return
		}
	}
	done := make(chan bool, 2)
	isConnect := false
	go func() {
		defer func() {
			done <- true
		}()
		if !isConnect {
			cmdBytes :=[]byte(cmd + "\n")
			if _, err := channel.Write(cmdBytes); nil != err {
					return
			}
			isConnect = true
		}
		select {}
	}()
	//第二个协程将远程主机的返回结果返回给用户
	go func() {
		for {
			defer func() {
				done <- true
			}()
			br := bufio.NewReader(channel)
			buf := []byte{}
			t := time.NewTimer(time.Millisecond * 100)
			defer t.Stop()
			r := make(chan rune)
			go func() {
				for {
					x, size, err := br.ReadRune()
					if err != nil {
						return
					}
					if size > 0 {
						r <- x
					}
				}
			}()
			for {
				select {
				case <-t.C:
					if len(buf) != 0 {
						err := ws.WriteMessage(websocket.TextMessage, buf)
						buf = []byte{}
						if err != nil {
							return
						}
					}
					t.Reset(time.Millisecond * 100)
				case d := <-r:
					if d != utf8.RuneError {
						p := make([]byte, utf8.RuneLen(d))
						utf8.EncodeRune(p, d)
						buf = append(buf, p...)
					} else {
						buf = append(buf, []byte("@")...)
					}
				}
			}
		}
	}()
	defer func() {
		if isConnect {
			channel.Close()
			client.Close()
		}
		ws.Close()
	}()
	<-done
}

